package pnca.analytics;

import pnca.*;
import pnca.Constants;
import MASS.*;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Level;


public class YourAnalyticsModule extends Place {
    
    // Constants
    // MASS arguments
    private static final String NAME = Constants.ANALYTICSPACKAGE
				+ "YourAnalyticsModule"; //fully qualified name
    private static final String[] MASSARGS = Constants.MASSARGS;
				// connection arguments
    private static final int NPROCESSES = Constants.NPROCESSES;
				// number of processes to employ
    private static final int NTHREADS = Constants.NTHREADS;
				// number of threads to employ
    private static int MAXITERATIONS = Constants.TALLIES;
    
    // Variables
    private static int fileRange;               // number of computing nodes
    private static Places pacNorthwest = null;  // MASS parallel environment
    private static ProvenanceAdapter pa;        // Stores provenance
    private static String[] fileNames;          // selected source files
    
    private static final int init_ = 0;         // initialize Places
    private static final int read_ = 1;    	// read temperatures
    private static final int analyze_ = 2;   	// expose days
    private static final int write_ = 3;   	// day to index mapping
    private static final int youruserdefinedfunction_ = 4;
    
   
    private static final String DATA = Constants.TEMP;
					// Temperature variable name
    private static final int METADATALENGTH = Constants.METADATALENGTH;
					// # of metadata elements
    private static final int XRANGE = Constants.XRANGE;
					// width of data set (# of grid cells)
    private static final int YRANGE = Constants.YRANGE;
					// length of data set (# of grid cells)
    
    private int myIndex;       	// computing node's index
    private double[][] data;   	// data
    
    /**
     * @name YourAnalyticsModule - 
     * @param interval unused Empty constructor required for MASS Place
     * implementation
     */
    public YourAnalyticsModule(Object interval) {
        // A constructor implementation is required for MASS Place
    }

    /**
     * @name callMethod Provides enumerated list of functions to be parallelized
     * in MASS
     *
     * @param funcId - function ID (listed as Constants)
     * @param args - function parameters
     * @return - unused
     */
    public Object callMethod(int funcId, Object args) {
        switch (funcId) {
            case init_:
                return init(args);
            case read_:
                return read(args);
            case analyze_:
                return analyze(args);
            case write_:
                return write(args);
            case youruserdefinedfunction_:
                return yourUserDefinedFunction(args);
        }
        return null;
    }
    
    /**
     * @name init - Initialization information for MASS Places 
     *
     * @param args - unused
     * @return unused
     */
    public Object init(Object args) {
        // Each place keeps track of its time position
        myIndex = index[0];
        
        data = new double[XRANGE][YRANGE];

        return (Object) null;
    }
    
    /**
     * @name read - Retrieve 2D array of climate data from input file
     *
     * @param args - unused
     * @return unused
     */
    public Object read(Object args) {
        try {
            // Retrieve input values from input adapter
            data = InputAdapter.read2DFloat(fileNames[myIndex], DATA, XRANGE,
		YRANGE);

        } catch (Exception ex) {
            pa.log(Level.SEVERE, "exception caught in readTemps", ex);
        }

        // Check if values were retrieved
        if (data == null) {
            System.err.println("Null input adapter return");
        }
        return (Object) null;
    }
    
    public Object analyze(Object args) {

        return (Object) null;
    }

    /**
     * @name write - Write results to netCDF file 
     *
     * @param args - unused
     * @return unused
     */
    public Object write(Object args) {
        try {
            // Write tally for this year to results file
            Integer[] myRanges = new Integer[2];    // two dimensions
            myRanges[0] = XRANGE;                   // west-east
            myRanges[1] = YRANGE;                   // south-north
            // create entry for data
            AbstractMap.SimpleEntry<String, Integer[]> myVar =
		new AbstractMap.SimpleEntry("YourAnalyticsModuleData",
		myRanges);
            ResultsAdapter dataTally = new ResultsAdapter(ResultsAdapter.DOUBLE,
		"YourAnalyticsModuleResults" + myIndex + ".nc",
		fileNames[myIndex], myVar);
            // log the tally
            dataTally.log2DDouble("YourAnalyticsModuleData", data);
            
        } catch (Exception ex) {
            pa.log(Level.SEVERE, "Your exception log message", ex);
        }
        return (Object) null;
    }
    
    /**
     * @name yourUserDefinedFunction - Sample user defined function
     *
     * @param args - input parameters must be wrapped in an object
     * @return - output must be wrapped in object
     */
    public Object yourUserDefinedFunction(Object args) {
        // User defined functions
        // This is where you manipulate and share data in order to implement
        // your data analysis algorithm.
        return (Object) null;
    }
    
    /**
     * @name setFiles - Set file name array to store file names
     * @param fileArray - Array containing output result file names
     */
    public static void setFiles(String[] fileArray) {
        fileNames = fileArray;
        fileRange = fileArray.length;
    }
    
    /**
     * @name initialize - Sets up the MASS library and optional provenance
     *
     * @param status - Real time message display for GUI
     * @param handle - MASS places identifier
     */
    public static void initialize(StatusAdapter status, int handle) {
        // Perform any necessary initialization
        pa.provenanceMessages = new ArrayList<String>();
        try {
            // Dummy object required by MASS
            Object[] fixer = new Object[fileRange];

            // Provenance collection
            status.reportMessage("Storing Metadata");
            AbstractMap.SimpleEntry<String, String>[] provenanceEntries =
	    	collectProvenance();
            pa = new ProvenanceAdapter("YourAnalyticsModuleProvenance",
		fileNames[0]);
            pa.create(provenanceEntries);
            
            // Start MASS
            status.reportMessage("Starting MASS");
            MASS.init(MASSARGS, NPROCESSES, NTHREADS);

            // Initialize parallel computing space (Places object)
            pacNorthwest = new Places(handle, NAME, null,
                    fileRange);
            status.reportMessage("Computational Nodes created");

            // Initialize all computing nodes
            pacNorthwest.callAll(init_, fixer);
            status.reportMessage("Computational Nodes initialized");
            
        } catch (Exception ex) {
            pa.log(Level.SEVERE, null, ex);
        }
        
    }
    
    /**
     * @name runAnalysis - Executes the parallelized analysis, analogous to
     *			   main()
     * @param status - Real time message display for GUI
     * @return time - Execution time
     */
    public static long runAnalysis(StatusAdapter status) {
        // runAnalysis acts as a 'main' driver function from which user defined
        // functions can be called
        
        long start, end; // Data provenance for execution time
        long totalTime = -1;

        // Dummy object to prevent array over- or underflow
        Object[] fixer = new Object[fileRange];
        
        try {
            // Begin time measurement
            start = new Date().getTime();

            // Example: read temperatures at each time slice
            pacNorthwest.callAll(read_, fixer);
            status.reportMessage("Source values read");
            
            // Example: analyze temperatures
            pacNorthwest.callAll(analyze_, fixer);
            status.reportMessage("Source values analyzed");
            
            // Example: write temperatures to netCDF files
            pacNorthwest.callAll(write_, fixer);
            status.reportMessage("Results stored");
            
            pa.provenanceMessages.add("Custom provenance message goes here");
            
            // End time measurement
            end = new Date().getTime();
            totalTime = end - start;    //milliseconds
            
            pa.logProvenance();
            status.reportMessage("Provenance stored");
            
            // Report execution time
            System.err.println("Segment execution time (milliseconds): "
                    + totalTime);
            status.reportMessage("Segment execution time (milliseconds): "
                    + totalTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        MASS.finish();
        return totalTime;
    }
    
    /**
     * @name collectProvenance - Returns a SimpleEntry array holding provenance
     * data
     * @return SimpleEntry - Holds provenance key-value pairs
     */
    private static AbstractMap.SimpleEntry<String,
    	String>[] collectProvenance() {
        AbstractMap.SimpleEntry<String, String>[] provenanceEntries =
		new AbstractMap.SimpleEntry[METADATALENGTH];
        provenanceEntries[0] = 
		new AbstractMap.SimpleEntry<>("P_Controlling_Node",
   		MASSARGS[0]);
        provenanceEntries[1] =
		new AbstractMap.SimpleEntry<>("P_Controlling_Node_Password",
		MASSARGS[1]);
        provenanceEntries[2] =
		new AbstractMap.SimpleEntry<>("P_Machine_Life_File_Name",
		MASSARGS[2]);
        provenanceEntries[3] =
		new AbstractMap.SimpleEntry<>("P_Port_Number", MASSARGS[3]);
        provenanceEntries[4] =
		new AbstractMap.SimpleEntry<>("P_No_of_Processes",
		String.valueOf(NPROCESSES));
        provenanceEntries[5] =
		new AbstractMap.SimpleEntry<>("P_No_of_Threads",
		String.valueOf(NTHREADS));
        provenanceEntries[6] =
		new AbstractMap.SimpleEntry<>("P_No_of_Nodes",
		String.valueOf(fileRange));

        return provenanceEntries;
    }
   
    public static int getMaxIterations() {
        return MAXITERATIONS;
    }
}